/*
 *  Copyright 2011 Quytelda K. Gaiwin
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.tamalin.nighthawk;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.crypto.Cipher;

import org.tamalin.nighthawk.engine.CipherEngine;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;

/**
 * The NighthawkActivity class represents the main part of the application.  It's view
 * contains the password box, plaintext area, and encrypt and decrypt buttons.
 * @author Quytelda K. Gaiwin
 * @since 1.0
 */
public class NighthawkActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        initComponents();
        
        
        /*
         * Do encryption/decryption if there is a mode specified in the loading of the activity.
         * When the app is first loaded there is no mode property, but when this activity has been restarted from
         * a FilenameActivity, it will receive a mode and other information through it's intent.
         */
        if(getIntent().hasExtra("mode"))
        {
        	Intent i = getIntent();
        	if(i.getExtras().getInt("mode") == Cipher.DECRYPT_MODE)
        	{
        		byte[] plaintext = decrypt(new File(i.getExtras().getString("filename")));
        		
        		if(plaintext == null) return;
        		EditText textView = (EditText) findViewById(R.id.plaintext);
        		textView.setText(new String(plaintext));
        	}
        	else
        	{
        		byte[] ciphertext = encrypt();
        		
        		if(ciphertext == null) return;
        		//Write ciphertext to file.
        		try
				{
					writeDataFile(new File(i.getExtras().getString("filename")), ciphertext);
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
        	}
        }
    }
    
    private void initComponents()
    {
    	Button encrypt = (Button) findViewById(R.id.encrypt);
        encrypt.setOnClickListener(new OnClickListener()
        {
			public void onClick(View v)
			{
				Intent i = new Intent(NighthawkActivity.this, FilenameActivity.class);
				i.putExtra("mode", Cipher.ENCRYPT_MODE);
				i.putExtra("data", new String(getData()));
				i.putExtra("password", getPassword());
				startActivity(i);
			}
        });
        
        Button decrypt = (Button) findViewById(R.id.decrypt);
        decrypt.setOnClickListener(new OnClickListener()
        {
			public void onClick(View v)
			{
				// Get filename to decrypt from
				Intent i = new Intent(NighthawkActivity.this, FilenameActivity.class);
				i.putExtra("mode", Cipher.DECRYPT_MODE);
				i.putExtra("password", getPassword());
				startActivity(i);
			}
        });
    }
    
    /**
     * Decrypts the contents of the given file and returns the plaintext as a byte array.
     * @param f The file to decrypt.
     * @return The plaintext from the decrypted file, or null if the key was invalid.
     */
    public byte[] decrypt(File f)
    {
    	if(!checkPresets())
    	{
			Toast.makeText(this, "Please provide a password!", Toast.LENGTH_LONG).show();
			return null;
    	}
    	
    	CipherEngine engine = CipherEngine.getInstance(ALGORITHM, this);
    	byte[] ciphertext = null;
    	try
		{
			ciphertext = readDataFile(f);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		char[] pwd = getIntent().getExtras().getCharArray("password");
		engine.init(ciphertext, pwd, Cipher.DECRYPT_MODE);
		byte[] plaintext = engine.start();
		
		return plaintext;
    }
    /**
     * Encrypts the contents of the plaintext area and returns the result as an array of bytes.
     * @return the encrypted ciphertext
     */
    public byte[] encrypt()
    {
    	if(!checkPresets())
    	{
			Toast.makeText(this, "Please provide a password!", Toast.LENGTH_LONG).show();
			return null;
    	}
    	
		CipherEngine engine = CipherEngine.getInstance(ALGORITHM, this);
		byte[] plaintext = getIntent().getExtras().getString("data").getBytes();
		char[] pwd = getIntent().getExtras().getCharArray("password");
		engine.init(plaintext, pwd, Cipher.ENCRYPT_MODE);
		byte[] ciphertext = engine.start();
				
		return ciphertext;
    }
    
    private boolean checkPresets()
    {
    	return getPassword() == null || getPassword().length == 0;
    }
    
    /**
     * Returns the contents of the password field as a char array.
     * @return a char array representing the password in the field.
     */
    private char[] getPassword()
    {
    	EditText password = (EditText) findViewById(R.id.password);
		return password.getText().toString().toCharArray();
    }
    
    /**
     * Returns the contents of the plaintext area as an array of bytes.
     * @return contents of the plaintext area
     */
    private byte[] getData()
    {
    	EditText data = (EditText) findViewById(R.id.plaintext);
    	return data.getText().toString().getBytes();
    }
    
    /**
     * Reads a file and returns the contents as an array of bytes.
     * @param f the file to read.
     * @return an array of bytes representing the file's contents.
     * @throws IOException an IOException is thrown if the file does not exist or an
     * error occurs while reading the file.
     */
    private byte[] readDataFile(File f) throws IOException
    {
    	File in = new File(Environment.getExternalStorageDirectory() + "/org.tamalin.nighthawk/" + f.getAbsolutePath());
    	Log.d("", in.getAbsolutePath());
    	
    	if(!in.exists())
    		return null;
    	
    	FileInputStream fis = new FileInputStream(in);
    	int length = (int) in.length();
    	byte[] data = new byte[length];
    	fis.read(data);
    	return data;
    }
    
    /**
     * Writes data to a file as an array of bytes.
     * @param f The file to write to
     * @param data the data to write to file
     * @throws IOException an IOException is thrown if the file does not exist
     * or the connection is interrupted.
     */
    private void writeDataFile(File f, byte[] data) throws IOException
    {
    	File out = new File(Environment.getExternalStorageDirectory() + "/org.tamalin.nighthawk/" + f.getAbsolutePath());
    	
    	if(out.isDirectory())
    	{
    		// AlertDialog alert = AlertDialog.
    		Toast.makeText(this, "File cannot be a directory.", Toast.LENGTH_LONG).show();
    		return;
    	}
    	
    	if(!out.exists())
    	{
    		if(!out.getParentFile().exists())
    		{
    			boolean created = out.getParentFile().mkdir();
    			if(!created)
    			{
    				throw new IOException();
    			}
    		}
    		boolean created = out.createNewFile();
    		if(!created) throw new IOException();
    	}
    	
    	FileOutputStream fos = new FileOutputStream(out);
    	fos.write(data);
    	fos.close();
    }
    
    /**
     * This constant represents the encryption algorithm used for encryption or decryption.
     */
    public static final String ALGORITHM = "AES";
}